#include "GWindow.h"
#include "GCamera.h"
#include "GRender.h"

bool GWindow::ms_bRegistedWndClass = false;
WNDCLASS* GWindow::ms_pWndClass = NULL;
WindowEvent* GWindow::ms_pEvent = new WindowEvent();

GWindow::GWindow(int nWidth, int nHeight)
{
	m_nWidth = nWidth;
	m_nHeight = nHeight;
	m_pFrameBuffer = NULL;
	m_pZBuffer = (float*)malloc(m_nWidth * m_nHeight * sizeof(float));
	memset(m_pZBuffer, 0, sizeof(float) * m_nHeight * m_nWidth);

	m_screenHandle = NULL;
	m_screenDc = NULL;
}

void GWindow::Init()
{
	TryRegisterWndClass();
	WNDCLASS wc = *ms_pWndClass;

	BITMAPINFO bi = { { sizeof(BITMAPINFOHEADER), m_nWidth, -m_nHeight, 1, 32, BI_RGB,
		m_nWidth * m_nHeight * 4, 0, 0, 0, 0 } };

	wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wc.hInstance = GetModuleHandle(NULL);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);

	m_screenHandle = CreateWindow(_T("GRenderClass"), _T("GRender"),
		WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
		0, 0, 0, 0, NULL, NULL, wc.hInstance, NULL);

	if (m_screenHandle == NULL) 
		return;

	HDC hDC = GetDC(m_screenHandle);
	m_screenDc = CreateCompatibleDC(hDC);
	ReleaseDC(m_screenHandle, hDC);

	HBITMAP	screen_hb = CreateDIBSection(m_screenDc, &bi, DIB_RGB_COLORS, (void**)&m_pFrameBuffer, 0, 0);
	if (screen_hb == NULL)
		return;
	SelectObject(m_screenDc, screen_hb);

	RECT rect = { 0, 0, m_nWidth, m_nHeight };
	int wx, wy, sx, sy;
	AdjustWindowRect(&rect, GetWindowLong(m_screenHandle, GWL_STYLE), 0);
	wx = rect.right - rect.left;
	wy = rect.bottom - rect.top;
	sx = (GetSystemMetrics(SM_CXSCREEN) - wx) / 2;
	sy = (GetSystemMetrics(SM_CYSCREEN) - wy) / 2;
	if (sy < 0) sy = 0;
	SetWindowPos(m_screenHandle, NULL, sx, sy, wx, wy, (SWP_NOCOPYBITS | SWP_NOZORDER | SWP_SHOWWINDOW));
	SetForegroundWindow(m_screenHandle);

	ShowWindow(m_screenHandle, SW_NORMAL);
}

void GWindow::Active(GCamera* pCamera)
{
	MSG msg;

	while (1)
	{
		if (!PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) break;
		if (!GetMessage(&msg, NULL, 0, 0)) break;
		DispatchMessage(&msg);
	}

	HDC hDC = GetDC(m_screenHandle);
	BitBlt(hDC, 0, 0, m_nWidth, m_nHeight, m_screenDc, 0, 0, SRCCOPY);
	ReleaseDC(m_screenHandle, hDC);
}

GRenderTarget* GWindow::GetRenderTarget()
{
	GRenderTarget* pTarget = new GRenderTarget();
	pTarget->byBufferLen = sizeof(unsigned int);
	pTarget->pRenderBuffer = m_pFrameBuffer;
	pTarget->pZBuffer = m_pZBuffer;
	pTarget->uHeight = m_nHeight;
	pTarget->uWidth = m_nWidth;
	return pTarget;
}

Matrix4f GWindow::GenViewPortMatrix()
{
	Matrix4f viewportMat;
	float fHalfHeight = (float)m_nHeight / 2.0f;
	float fHalfWidth = (float)m_nWidth / 2.0f;
	viewportMat << fHalfWidth, 0, 0, fHalfWidth,
		0, fHalfHeight, 0, fHalfHeight,
		0, 0, 1, 0,
		0, 0, 0, 1;
	return viewportMat;
}

Vector2f GWindow::GetMousePos()
{
	POINT point;
	GetCursorPos(&point);
	ScreenToClient(ms_pEvent->pCurrentWindow->GetWndHandle(), &point);
	return Vector2f((float)point.x, (float)point.y);
}

HWND GWindow::GetWndHandle()
{
	return m_screenHandle;
}

void GWindow::DispatchMsg()
{
	MSG msg;

	while (1)
	{
		if (!PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) break;
		if (!GetMessage(&msg, NULL, 0, 0)) break;
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

void GWindow::WindowDraw()
{
	HDC hDC = GetDC(m_screenHandle);
	BitBlt(hDC, 0, 0, m_nWidth, m_nHeight, m_screenDc, 0, 0, SRCCOPY);
	ReleaseDC(m_screenHandle, hDC);
}

void GWindow::HandleEvent(GCamera* pCamera)
{
	pCamera->UpdateCameraAxis();
	HandleMouseEvent(pCamera);
	pCamera->UpdateMatrix();
}

LRESULT GWindow::ProcessEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg) 
	{
		case WM_CLOSE:break;
		case WM_KEYDOWN:
			ms_pEvent->keys[wParam & 511] = 1;
			break;
		case WM_KEYUP:
			ms_pEvent->keys[wParam & 511] = 0;
			break;
		case WM_LBUTTONDOWN:
			ms_pEvent->buttons[0] = 1;
			ms_pEvent->mouseInfo.orbit_pos = GetMousePos();
			break;
		case WM_LBUTTONUP:
			ms_pEvent->buttons[0] = 0;
			break;
		case WM_RBUTTONDOWN:
			ms_pEvent->mouseInfo.fv_pos = GetMousePos();
			ms_pEvent->buttons[1] = 1;
			break;
		case WM_RBUTTONUP:
			ms_pEvent->buttons[1] = 0;
			break;
		case WM_MOUSEWHEEL:
			ms_pEvent->mouseInfo.wheel_delta = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
			break;
		default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}

void GWindow::HandleMouseEvent(GCamera* pCamera)
{
	if (ms_pEvent->buttons[0])
	{
		Vector2f cur_pos = GetMousePos();
		ms_pEvent->mouseInfo.orbit_delta = ms_pEvent->mouseInfo.orbit_pos - cur_pos;
		ms_pEvent->mouseInfo.orbit_pos = cur_pos;
	}

	if (ms_pEvent->buttons[1])
	{
		Vector2f cur_pos = GetMousePos();
		ms_pEvent->mouseInfo.fv_delta = ms_pEvent->mouseInfo.fv_pos - cur_pos;
		ms_pEvent->mouseInfo.fv_pos = cur_pos;
	}

	UpdateCameraPos(pCamera);
	ms_pEvent->mouseInfo.wheel_delta = 0.0f;
}

void GWindow::UpdateCameraPos(GCamera* pCamera)
{
	Vector3f from_target = pCamera->m_vEye - pCamera->m_vTarget;
	float factor = 1.5 * PI;
	float radius = from_target.norm();
	float phi = (float)atan2(from_target[0], from_target[2]);
	float theta = (float)acos(from_target[1] / radius);
	float x_delta = ms_pEvent->mouseInfo.orbit_delta[0] / m_nWidth;
	float y_delta = ms_pEvent->mouseInfo.orbit_delta[1] / m_nHeight;

	radius *= (float)pow(0.95, ms_pEvent->mouseInfo.wheel_delta);

	phi += x_delta * factor;
	theta += y_delta * factor;
	if (theta > PI) theta = PI - 1e-3f;
	if (theta < 0) theta = 1e-3f;

	pCamera->m_vEye[0] = pCamera->m_vTarget[0] + radius * sin(phi) * sin(theta);
	pCamera->m_vEye[1] = pCamera->m_vTarget[1] + radius * cos(theta);
	pCamera->m_vEye[2] = pCamera->m_vTarget[2] + radius * sin(theta) * cos(phi);

	factor = radius * (float)tan(60.0 / 360 * PI) * 2.2;
	x_delta = ms_pEvent->mouseInfo.fv_delta[0] / m_nWidth;
	y_delta = ms_pEvent->mouseInfo.fv_delta[1] / m_nHeight;
	Vector3f left = x_delta * factor * pCamera->x;
	Vector3f up = y_delta * factor * pCamera->y;

	pCamera->m_vEye += (left - up);
	pCamera->m_vTarget += (left - up);
}

void GWindow::TryRegisterWndClass()
{
	if (ms_bRegistedWndClass)
		return;
	
	WNDCLASS wc = { CS_BYTEALIGNCLIENT, (WNDPROC)&GWindow::ProcessEvent, 0, 0, 0,
	NULL, NULL, NULL, NULL, _T("GRenderClass") };
	ms_bRegistedWndClass = RegisterClass(&wc);
	ms_pWndClass = &wc;
}